#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import os
import numpy as np
import glob
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plt
import pandas as pd
import datetime
import matplotlib.dates as mdate
from matplotlib.figure import Figure
import io
try:
    from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                                 QHBoxLayout, QPushButton, QLabel, QFileDialog,
                                 QMessageBox, QTabWidget, QTextEdit, QGroupBox,
                                 QLineEdit, QProgressBar, QComboBox, QSpinBox,
                                 QDoubleSpinBox, QCheckBox, QSplitter, QDateTimeEdit)
    from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QDateTime
    from PyQt5.QtGui import QFont, QIcon, QPixmap
    PYQT_VERSION = 5
except ImportError:
    from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                                 QHBoxLayout, QPushButton, QLabel, QFileDialog,
                                 QMessageBox, QTabWidget, QTextEdit, QGroupBox,
                                 QLineEdit, QProgressBar, QComboBox, QSpinBox,
                                 QDoubleSpinBox, QCheckBox, QSplitter, QDateTimeEdit)
    from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer, QDateTime
    from PyQt6.QtGui import QFont, QIcon, QPixmap
    PYQT_VERSION = 6

class DataProcessor(QThread):
    """数据处理线程"""
    progress_signal = pyqtSignal(int)
    log_signal = pyqtSignal(str)
    finished_signal = pyqtSignal(dict)
    
    def __init__(self, data_type, input_path, output_dir, fits_keys=None, output_csv_path=None):
        super().__init__()
        self.data_type = data_type  # 'log' or 'fits'
        self.input_path = input_path
        self.output_dir = output_dir
        # FITS字段名配置（默认值）
        default_keys = {
            'date': 'DATE-OBS',
            'temp': 'TEMP',
            'vacuum': 'VACCUM',
            'frametype': 'HIERARCH FRAMETYPE',
        }
        self.fits_keys = default_keys if fits_keys is None else {**default_keys, **fits_keys}
        self.output_csv_path = output_csv_path
        
    def run(self):
        try:
            if self.data_type == 'log':
                result = self.process_log_data()
            else:
                result = self.process_fits_data()
            
            self.finished_signal.emit(result)
        except Exception as e:
            self.log_signal.emit(f"处理错误: {str(e)}")
            self.finished_signal.emit({'error': str(e)})
    
    def process_log_data(self):
        """处理日志文件数据"""
        self.log_signal.emit("开始处理日志文件...")
        
        # 读取日志文件
        with open(self.input_path, 'r') as fp:
            lines = fp.readlines()
        
        t_date = []
        v_date = []
        temp = []
        vaccum = []
        # 新增CCD温度数据
        ccd1_temp = []
        ccd2_temp = []
        ccd3_temp = []
        ccd4_temp = []
        ccd_dates = []
        # 新增Drive_circuit和Dewar温度数据
        drive_circuit_temp = []
        dewar_temp = []
        drive_dewar_dates = []
        
        for i, line in enumerate(lines):
            if i % 1000 == 0:
                self.progress_signal.emit(int(i * 100 / len(lines)))
                
            if 'hwStatus.currentTemp' in line:
                t_date.append(line[1:23].replace(' ', 'T'))
                temp.append(float(line.split(':')[-1]))
            
            if 'hwStatus.currentVacuum' in line:
                v_date.append(line[1:23].replace(' ', 'T'))
                vaccum.append(float(line.strip().split(':')[-1]))
            
            # 提取CCD温度数据
            if 'Received MCU response:' in line and 'CCD_1:' in line and 'CCD_2:' in line and 'CCD_3:' in line and 'CCD_4:' in line:
                try:
                    # 解析CCD温度数据
                    response_part = line.split('Received MCU response:')[1].strip()
                    ccd_parts = response_part.split('&&')
                    
                    ccd1_val = None
                    ccd2_val = None
                    ccd3_val = None
                    ccd4_val = None
                    
                    for part in ccd_parts:
                        if part.startswith('CCD_1:'):
                            ccd1_val = float(part.split(':')[1])
                        elif part.startswith('CCD_2:'):
                            ccd2_val = float(part.split(':')[1])
                        elif part.startswith('CCD_3:'):
                            ccd3_val = float(part.split(':')[1])
                        elif part.startswith('CCD_4:'):
                            ccd4_val = float(part.split(':')[1])
                    
                    # 只有当所有CCD数据都解析成功时才添加
                    if all(val is not None for val in [ccd1_val, ccd2_val, ccd3_val, ccd4_val]):
                        ccd_date = line[1:23].replace(' ', 'T')
                        ccd_dates.append(ccd_date)
                        ccd1_temp.append(ccd1_val)
                        ccd2_temp.append(ccd2_val)
                        ccd3_temp.append(ccd3_val)
                        ccd4_temp.append(ccd4_val)
                except Exception as e:
                    # 解析失败时跳过这一行
                    continue
            
            # 提取Drive_circuit和Dewar温度数据
            if 'Received MCU response:' in line and 'Drive_circuit:' in line and 'Dewar:' in line:
                try:
                    # 解析Drive_circuit和Dewar温度数据
                    response_part = line.split('Received MCU response:')[1].strip()
                    parts = response_part.split('&&')
                    
                    drive_circuit_val = None
                    dewar_val = None
                    
                    for part in parts:
                        if part.startswith('Drive_circuit:'):
                            drive_circuit_val = float(part.split(':')[1])
                        elif part.startswith('Dewar:'):
                            dewar_val = float(part.split(':')[1])
                    
                    # 只有当两个数据都解析成功时才添加
                    if drive_circuit_val is not None and dewar_val is not None:
                        drive_dewar_date = line[1:23].replace(' ', 'T')
                        drive_dewar_dates.append(drive_dewar_date)
                        drive_circuit_temp.append(drive_circuit_val)
                        dewar_temp.append(dewar_val)
                except Exception as e:
                    # 解析失败时跳过这一行
                    continue
        
        self.log_signal.emit(f"提取到 {len(temp)} 个温度数据点，{len(vaccum)} 个真空度数据点")
        self.log_signal.emit(f"提取到 {len(ccd1_temp)} 个CCD温度数据点")
        self.log_signal.emit(f"提取到 {len(drive_circuit_temp)} 个Drive_circuit和Dewar温度数据点")

        t_file = f"{self.output_dir}/temp.txt"
        v_file = f"{self.output_dir}/vaccum.txt"
        fp_t = open(t_file,'w')
        fp_v = open(v_file,'w')
        
        # 写入温度数据（平均温度、CCD温度和Drive_circuit/Dewar温度）
        max_len = max(len(t_date), len(ccd1_temp), len(drive_circuit_temp))
        for i in range(max_len):
            # 写入平均温度（如果存在）
            if i < len(t_date):
                fp_t.write('{}\t{}\n'.format(t_date[i], temp[i]))
            else:
                fp_t.write('\n')  # 空行占位
            
            # 写入CCD温度（如果存在）
            if i < len(ccd1_temp):
                fp_t.write('{}\tCCD1:{}\tCCD2:{}\tCCD3:{}\tCCD4:{}\n'.format(
                    ccd_dates[i], ccd1_temp[i], ccd2_temp[i], ccd3_temp[i], ccd4_temp[i]))
            else:
                fp_t.write('\n')  # 空行占位
                
            # 写入Drive_circuit和Dewar温度（如果存在）
            if i < len(drive_circuit_temp):
                fp_t.write('{}\tDrive_circuit:{}\tDewar:{}\n'.format(
                    drive_dewar_dates[i], drive_circuit_temp[i], dewar_temp[i]))
            else:
                fp_t.write('\n')  # 空行占位
        
        fp_t.close()

        for i in range(len(v_date)):
            fp_v.write('{}\t{}\n'.format(v_date[i],vaccum[i]))
        fp_v.close()

        self.log_signal.emit(f"温度、真空度数据写入{t_file}, {v_file}")
        
        return {
            't_date': np.array(t_date),
            'temp': np.array(temp),
            'v_date': np.array(v_date),
            'vaccum': np.array(vaccum),
            'ccd1_temp': np.array(ccd1_temp),
            'ccd2_temp': np.array(ccd2_temp),
            'ccd3_temp': np.array(ccd3_temp),
            'ccd4_temp': np.array(ccd4_temp),
            'ccd_dates': np.array(ccd_dates),
            'drive_circuit_temp': np.array(drive_circuit_temp),
            'dewar_temp': np.array(dewar_temp),
            'drive_dewar_dates': np.array(drive_dewar_dates),
            'type': 'log'
        }
    
    def process_fits_data(self):
        """处理FITS文件数据"""
        self.log_signal.emit("开始处理FITS文件...")
        
        flist = glob.glob(f'{self.input_path}/*.fits')
        if not flist:
            raise ValueError("未找到FITS文件")
        
        date_obs = []
        temp = []
        vaccum = []
        ccd_idx = []
        imgtype = []
        # 新增字段
        exptime = []
        instrume = []
        readouts = []
        filename = []
        
        # 读取键名
        date_key = self.fits_keys.get('date', 'DATE-OBS')
        temp_key = self.fits_keys.get('temp', 'TEMP')
        vacuum_key = self.fits_keys.get('vacuum', 'VACCUM')
        frametype_key = self.fits_keys.get('frametype', 'HIERARCH FRAMETYPE')
        
        for i, f in enumerate(flist):
            if i % 10 == 0:
                self.progress_signal.emit(int(i * 100 / len(flist)))
                self.log_signal.emit(f"处理文件 {i+1}/{len(flist)}: {os.path.basename(f)}")
            
            try:
                # 使用astropy读取FITS文件，如果失败则跳过
                from astropy.io import fits
                with fits.open(f) as hdu:
                    hdr = hdu[0].header
                    dobj = self._get_header_value(hdr, date_key, default='Unknown')
                    tobj = self._get_header_value(hdr, temp_key, default=0)
                    vobj = self._get_header_value(hdr, vacuum_key, default=0)
                    fobj = self._get_header_value(hdr, frametype_key, default='unknown')
                    # 其他字段采用固定常用键名
                    exp_obj = hdr.get('EXPTIME', None)
                    inst_obj = hdr.get('INSTRUME', None)
                    rdot_obj = hdr.get('READOUTS', None)
                    
                    filename.append(f)
                    date_obs.append(dobj)
                    try:
                        temp.append(float(tobj))
                    except Exception:
                        temp.append(0.0)
                    try:
                        vaccum.append(float(vobj))
                    except Exception:
                        vaccum.append(0.0)
                    ccd_idx.append(f.split('_')[-1].replace('.fits', ''))
                    imgtype.append(str(fobj).strip())
                    # 追加其他字段
                    try:
                        exptime.append(float(exp_obj) if exp_obj is not None else None)
                    except Exception:
                        exptime.append(None)
                    instrume.append(None if inst_obj is None else str(inst_obj))
                    readouts.append(None if rdot_obj is None else str(rdot_obj))
            except Exception as e:
                self.log_signal.emit(f"跳过文件 {f}: {str(e)}")
                continue
        
        if not date_obs:
            raise ValueError("没有成功读取任何FITS文件")
        
        # 创建数据字典
        data_dict = {
            'FILENAME': filename,
            'DATE_OBS': date_obs,
            'EXPTIME': exptime,
            'INSTRUME': instrume,
            'READOUTS': readouts,
            'TEMP': temp,
            'VACCUM': vaccum,
            'CCD_IDX': ccd_idx,
            'TYPE': imgtype
        }
        
        # 按时间排序
        sorted_indices = np.argsort(date_obs)
        for key in data_dict:
            data_dict[key] = [data_dict[key][i] for i in sorted_indices]
        
        # 保存CSV元数据
        if self.output_csv_path:
            try:
                import pandas as _pd
                df = _pd.DataFrame(data_dict)
                csv_path = self.output_csv_path
                df.to_csv(csv_path, index=False)
                self.log_signal.emit(f"FITS元数据已保存: {csv_path}")
            except Exception as e:
                self.log_signal.emit(f"保存FITS元数据失败: {str(e)}")
 
        self.log_signal.emit(f"处理完成，共 {len(data_dict['DATE_OBS'])} 个数据点")
        
        return {
            'data': data_dict,
            'type': 'fits'
        }

    def _get_header_value(self, header, key, default=None):
        """按多种候选形式查找FITS Header中的键名。"""
        candidates = []
        if key is None or str(key).strip() == '':
            return default
        key = str(key).strip()
        candidates.append(key)
        candidates.append(key.upper())
        if not key.startswith('HIERARCH'):
            candidates.append(f'HIERARCH {key}')
            candidates.append(f'HIERARCH {key.upper()}')
        for k in candidates:
            try:
                val = header.get(k)
            except Exception:
                val = None
            if val is not None:
                return val
        return default

# ================= 无界面绘图保存函数 =================

def save_log_plot(data, file_path, y_limits=None):
    fig = Figure(figsize=(12, 9))
    # 第一个子图：CCD温度图
    ax1 = fig.add_subplot(3, 1, 1)
    t_date, temp = data['t_date'], data['temp']
    timestamp = pd.to_datetime(t_date)
    ax1.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    ax1.plot(timestamp, temp, '-', label='CCD mean Temp: bias', linewidth=0.3, color='black')
    # 添加CCD1-CCD4温度曲线
    if 'ccd1_temp' in data and len(data['ccd1_temp']) > 0:
        ccd_timestamp = pd.to_datetime(data['ccd_dates'])
        ax1.plot(ccd_timestamp, data['ccd1_temp'], '-', label='CCD1 Temp', linewidth=0.3, color='red')
        ax1.plot(ccd_timestamp, data['ccd2_temp'], '-', label='CCD2 Temp', linewidth=0.3, color='blue')
        ax1.plot(ccd_timestamp, data['ccd3_temp'], '-', label='CCD3 Temp', linewidth=0.3, color='green')
        ax1.plot(ccd_timestamp, data['ccd4_temp'], '-', label='CCD4 Temp', linewidth=0.3, color='orange')
    # 轴范围
    if y_limits and y_limits.get('temp'):
        ax1.set_ylim(y_limits['temp'])
    else:
        ax1.set_ylim([-100.75, -98.75])
    ax1.set_ylabel('CCD mean Temp (C)')
    ax1.tick_params(axis='x', rotation=20)
    ax1.legend(loc='upper center', bbox_to_anchor=(0.5, 1.02), ncol=5)
    ax1.grid(True, alpha=0.3)
    # 第二个子图：Drive_circuit和Dewar温度图
    ax2 = fig.add_subplot(3, 1, 2)
    if 'drive_circuit_temp' in data and len(data['drive_circuit_temp']) > 0:
        drive_dewar_timestamp = pd.to_datetime(data['drive_dewar_dates'])
        ax2.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
        ax2.plot(drive_dewar_timestamp, data['drive_circuit_temp'], '-', label='Drive_circuit Temp', linewidth=0.5, color='purple')
        ax2.plot(drive_dewar_timestamp, data['dewar_temp'], '-', label='Dewar Temp', linewidth=0.5, color='brown')
    if y_limits and y_limits.get('drive'):
        ax2.set_ylim(y_limits['drive'])
    ax2.set_ylabel('Temperature (C)')
    ax2.tick_params(axis='x', rotation=20)
    ax2.legend(loc='upper right')
    ax2.grid(True, alpha=0.3)
    ax2.set_title('Drive_circuit and Dewar Temperature')
    # 第三个子图：真空度图
    ax3 = fig.add_subplot(3, 1, 3)
    v_date, vaccum = data['v_date'], data['vaccum']
    ts_v = pd.to_datetime(v_date)
    index = vaccum > 0
    ax3.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    ax3.semilogy(ts_v[index], vaccum[index], 'o', ms=0.5, label='Vacuum')
    if y_limits and y_limits.get('vacuum'):
        ax3.set_ylim(y_limits['vacuum'])
    else:
        if len(vaccum[index]) > 0:
            ax3.set_ylim([min(vaccum[index]) * 0.9, max(vaccum[index]) * 1.1])
    ax3.set_ylabel('Vacuum (Pa)')
    ax3.tick_params(axis='x', rotation=20)
    ax3.legend(loc='upper right')
    ax3.grid(True, alpha=0.3)
    fig.tight_layout()
    fig.savefig(file_path, dpi=300, bbox_inches='tight')

def render_log_qpixmap(data) -> QPixmap:
    fig = Figure(figsize=(12, 9))
    # 第一个子图：CCD温度图
    ax1 = fig.add_subplot(3, 1, 1)
    t_date, temp = data['t_date'], data['temp']
    timestamp = pd.to_datetime(t_date)
    ax1.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    ax1.plot(timestamp, temp, '-', label='CCD mean Temp: bias', linewidth=0.3, color='black')
    if 'ccd1_temp' in data and len(data['ccd1_temp']) > 0:
        ccd_timestamp = pd.to_datetime(data['ccd_dates'])
        ax1.plot(ccd_timestamp, data['ccd1_temp'], '-', label='CCD1 Temp', linewidth=0.3, color='red')
        ax1.plot(ccd_timestamp, data['ccd2_temp'], '-', label='CCD2 Temp', linewidth=0.3, color='blue')
        ax1.plot(ccd_timestamp, data['ccd3_temp'], '-', label='CCD3 Temp', linewidth=0.3, color='green')
        ax1.plot(ccd_timestamp, data['ccd4_temp'], '-', label='CCD4 Temp', linewidth=0.3, color='orange')
    # 取消固定辅助线，使用更宽的默认范围
    ax1.set_ylim([-102.25, -97.75])
    ax1.set_ylabel('CCD mean Temp (C)')
    ax1.tick_params(axis='x', rotation=20)
    ax1.legend(loc='upper center', bbox_to_anchor=(0.5, 1.02), ncol=5)
    ax1.grid(True, alpha=0.3)
    # 第二个子图
    ax2 = fig.add_subplot(3, 1, 2)
    if 'drive_circuit_temp' in data and len(data['drive_circuit_temp']) > 0:
        drive_dewar_timestamp = pd.to_datetime(data['drive_dewar_dates'])
        ax2.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
        ax2.plot(drive_dewar_timestamp, data['drive_circuit_temp'], '-', label='Drive_circuit Temp', linewidth=0.5, color='purple')
        ax2.plot(drive_dewar_timestamp, data['dewar_temp'], '-', label='Dewar Temp', linewidth=0.5, color='brown')
    ax2.set_ylabel('Temperature (C)')
    ax2.tick_params(axis='x', rotation=20)
    ax2.legend(loc='upper right')
    ax2.grid(True, alpha=0.3)
    ax2.set_title('Drive_circuit and Dewar Temperature')
    # 第三个子图
    ax3 = fig.add_subplot(3, 1, 3)
    v_date, vaccum = data['v_date'], data['vaccum']
    ts_v = pd.to_datetime(v_date)
    index = vaccum > 0
    ax3.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    ax3.semilogy(ts_v[index], vaccum[index], 'o', ms=0.5, label='Vacuum')
    if len(vaccum[index]) > 0:
        ax3.set_ylim([min(vaccum[index]) * 0.9, max(vaccum[index]) * 1.1])
    ax3.set_ylabel('Vacuum (Pa)')
    ax3.tick_params(axis='x', rotation=20)
    ax3.legend(loc='upper right')
    ax3.grid(True, alpha=0.3)
    fig.tight_layout()
    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi=300, bbox_inches='tight')
    pix = QPixmap()
    pix.loadFromData(buf.getvalue())
    return pix


def save_fits_plot(data, file_path):
    fig = Figure(figsize=(10, 6))
    data_dict = data['data']
    index = [i for i, idx in enumerate(data_dict['CCD_IDX']) if idx == '00']
    if not index:
        index = list(range(len(data_dict['DATE_OBS'])))
    date = [data_dict['DATE_OBS'][i] for i in index]
    temp = [data_dict['TEMP'][i] for i in index]
    vacu = [float(data_dict['VACCUM'][i]) for i in index]
    imgtype = [data_dict['TYPE'][i] for i in index]
    idx_bs = [i for i, t in enumerate(imgtype) if t == 'bs']
    idx_dk = [i for i, t in enumerate(imgtype) if t == 'dk']
    idx_sf = [i for i, t in enumerate(imgtype) if t == 'sf']
    timestamp = pd.to_datetime(date)
    
    # 温度图
    ax1 = fig.add_subplot(2, 1, 1)
    ax1.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    if idx_bs:
        ax1.plot(timestamp[idx_bs], [temp[i] for i in idx_bs], 'o', label='CCD mean Temp: bias')
    if idx_dk:
        ax1.plot(timestamp[idx_dk], [temp[i] for i in idx_dk], 's', label='CCD mean Temp: dark')
    if idx_sf:
        ax1.plot(timestamp[idx_sf], [temp[i] for i in idx_sf], 'v', label='CCD mean Temp: flat')
    ax1.set_ylim([-101.25, -98.75])
    ax1.set_ylabel('CCD mean Temp (C)')
    ax1.tick_params(axis='x', rotation=20)
    ax1.legend(loc='upper center', bbox_to_anchor=(0.5, 1.02), ncol=3)
    ax1.grid(True, alpha=0.3)
    
    # 真空度图
    ax2 = fig.add_subplot(2, 1, 2)
    ax2.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    if idx_bs:
        ax2.semilogy(timestamp[idx_bs], [vacu[i] for i in idx_bs], 'o', label='Vacuum: bias')
    if idx_dk:
        ax2.semilogy(timestamp[idx_dk], [vacu[i] for i in idx_dk], 's', label='Vacuum: dark')
    if idx_sf:
        ax2.semilogy(timestamp[idx_sf], [vacu[i] for i in idx_sf], 'v', label='Vacuum: flat')
    if vacu:
        ax2.set_ylim([min(vacu) * 0.9, max(vacu) * 1.1])
    ax2.set_ylabel('Vacuum (Pa)')
    ax2.tick_params(axis='x', rotation=20)
    ax2.legend(loc='upper right')
    ax2.grid(True, alpha=0.3)
    
    fig.tight_layout()
    fig.savefig(file_path, dpi=300, bbox_inches='tight')

def render_fits_qpixmap(data) -> QPixmap:
    fig = Figure(figsize=(10, 6))
    data_dict = data['data']
    index = [i for i, idx in enumerate(data_dict['CCD_IDX']) if idx == '00']
    if not index:
        index = list(range(len(data_dict['DATE_OBS'])))
    date = [data_dict['DATE_OBS'][i] for i in index]
    temp = [data_dict['TEMP'][i] for i in index]
    vacu = [float(data_dict['VACCUM'][i]) for i in index]
    imgtype = [data_dict['TYPE'][i] for i in index]
    idx_bs = [i for i, t in enumerate(imgtype) if t == 'bs']
    idx_dk = [i for i, t in enumerate(imgtype) if t == 'dk']
    idx_sf = [i for i, t in enumerate(imgtype) if t == 'sf']
    timestamp = pd.to_datetime(date)
    ax1 = fig.add_subplot(2, 1, 1)
    ax1.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    if idx_bs:
        ax1.plot(timestamp[idx_bs], [temp[i] for i in idx_bs], 'o', label='CCD mean Temp: bias')
    if idx_dk:
        ax1.plot(timestamp[idx_dk], [temp[i] for i in idx_dk], 's', label='CCD mean Temp: dark')
    if idx_sf:
        ax1.plot(timestamp[idx_sf], [temp[i] for i in idx_sf], 'v', label='CCD mean Temp: flat')
    ax1.set_ylim([-101.25, -98.75])
    ax1.set_ylabel('CCD mean Temp (C)')
    ax1.tick_params(axis='x', rotation=20)
    ax1.legend(loc='upper center', bbox_to_anchor=(0.5, 1.02), ncol=3)
    ax1.grid(True, alpha=0.3)
    ax2 = fig.add_subplot(2, 1, 2)
    ax2.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    if idx_bs:
        ax2.semilogy(timestamp[idx_bs], [vacu[i] for i in idx_bs], 'o', label='Vacuum: bias')
    if idx_dk:
        ax2.semilogy(timestamp[idx_dk], [vacu[i] for i in idx_dk], 's', label='Vacuum: dark')
    if idx_sf:
        ax2.semilogy(timestamp[idx_sf], [vacu[i] for i in idx_sf], 'v', label='Vacuum: flat')
    if vacu:
        ax2.set_ylim([min(vacu) * 0.9, max(vacu) * 1.1])
    ax2.set_ylabel('Vacuum (Pa)')
    ax2.tick_params(axis='x', rotation=20)
    ax2.legend(loc='upper right')
    ax2.grid(True, alpha=0.3)
    fig.tight_layout()
    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi=300, bbox_inches='tight')
    pix = QPixmap()
    pix.loadFromData(buf.getvalue())
    return pix

def render_log_qpixmap_sized(data, width_px: int, height_px: int) -> QPixmap:
    dpi = 100
    fig = Figure(figsize=(max(1, width_px) / dpi, max(1, height_px) / dpi))
    # 第一个子图：CCD温度图
    ax1 = fig.add_subplot(3, 1, 1)
    t_date, temp = data['t_date'], data['temp']
    timestamp = pd.to_datetime(t_date)
    ax1.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    ax1.plot(timestamp, temp, '-', label='CCD mean Temp: bias', linewidth=0.3, color='black')
    if 'ccd1_temp' in data and len(data['ccd1_temp']) > 0:
        ccd_timestamp = pd.to_datetime(data['ccd_dates'])
        ax1.plot(ccd_timestamp, data['ccd1_temp'], '-', label='CCD1 Temp', linewidth=0.3, color='red')
        ax1.plot(ccd_timestamp, data['ccd2_temp'], '-', label='CCD2 Temp', linewidth=0.3, color='blue')
        ax1.plot(ccd_timestamp, data['ccd3_temp'], '-', label='CCD3 Temp', linewidth=0.3, color='green')
        ax1.plot(ccd_timestamp, data['ccd4_temp'], '-', label='CCD4 Temp', linewidth=0.3, color='orange')
    ax1.set_ylim([-102.25, -97.75])
    ax1.set_ylabel('CCD mean Temp (C)')
    ax1.tick_params(axis='x', rotation=20)
    ax1.legend(loc='upper center', bbox_to_anchor=(0.5, 1.02), ncol=5)
    ax1.grid(True, alpha=0.3)
    # 第二个子图
    ax2 = fig.add_subplot(3, 1, 2)
    if 'drive_circuit_temp' in data and len(data['drive_circuit_temp']) > 0:
        drive_dewar_timestamp = pd.to_datetime(data['drive_dewar_dates'])
        ax2.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
        ax2.plot(drive_dewar_timestamp, data['drive_circuit_temp'], '-', label='Drive_circuit Temp', linewidth=0.5, color='purple')
        ax2.plot(drive_dewar_timestamp, data['dewar_temp'], '-', label='Dewar Temp', linewidth=0.5, color='brown')
    ax2.set_ylabel('Temperature (C)')
    ax2.tick_params(axis='x', rotation=20)
    ax2.legend(loc='upper right')
    ax2.grid(True, alpha=0.3)
    ax2.set_title('Drive_circuit and Dewar Temperature')
    # 第三个子图
    ax3 = fig.add_subplot(3, 1, 3)
    v_date, vaccum = data['v_date'], data['vaccum']
    ts_v = pd.to_datetime(v_date)
    index = vaccum > 0
    ax3.xaxis.set_major_formatter(mdate.DateFormatter('%m-%d %H:%M'))
    ax3.semilogy(ts_v[index], vaccum[index], 'o', ms=0.5, label='Vacuum')
    if len(vaccum[index]) > 0:
        ax3.set_ylim([min(vaccum[index]) * 0.9, max(vaccum[index]) * 1.1])
    ax3.set_ylabel('Vacuum (Pa)')
    ax3.tick_params(axis='x', rotation=20)
    ax3.legend(loc='upper right')
    ax3.grid(True, alpha=0.3)
    fig.tight_layout()
    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi=dpi, bbox_inches='tight')
    pix = QPixmap()
    pix.loadFromData(buf.getvalue())
    return pix

def render_fits_qpixmap_sized(data) -> QPixmap:
    # 使用与现有render_fits_qpixmap相同逻辑，但尺寸由调用处缩放
    return render_fits_qpixmap(data)

# ====================================================

class LogDataTab(QWidget):
    """日志数据处理标签页（直接保存图片，不显示）"""
    def __init__(self, log_widget, preview_label):
        super().__init__()
        self.log_widget = log_widget
        self.preview_label = preview_label
        self.processor = None
        self.setup_ui()
    
    def setup_ui(self):
        layout = QVBoxLayout(self)
        
        # 文件选择组
        file_group = QGroupBox("文件选择")
        file_layout = QVBoxLayout(file_group)
        
        input_layout = QHBoxLayout()
        self.input_edit = QLineEdit()
        self.input_edit.setPlaceholderText("选择日志文件...")
        self.input_btn = QPushButton("浏览")
        self.input_btn.clicked.connect(self.browse_input)
        input_layout.addWidget(QLabel("日志文件:"))
        input_layout.addWidget(self.input_edit)
        input_layout.addWidget(self.input_btn)
        
        output_layout = QHBoxLayout()
        self.output_edit = QLineEdit()
        self.output_edit.setPlaceholderText("选择输出目录...")
        self.output_btn = QPushButton("浏览")
        self.output_btn.clicked.connect(self.browse_output)
        output_layout.addWidget(QLabel("输出目录:"))
        output_layout.addWidget(self.output_edit)
        output_layout.addWidget(self.output_btn)
        
        # 新增文件名输入框
        filename_layout = QHBoxLayout()
        self.filename_edit = QLineEdit("")
        filename_layout.addWidget(QLabel("文件名:"))
        filename_layout.addWidget(self.filename_edit)
        # 保存文件开关
        save_row = QHBoxLayout()
        self.save_check = QCheckBox("保存文件")
        self.save_check.setChecked(True)
        save_row.addWidget(self.save_check)
        # 纵坐标设置
        y_group = QGroupBox("纵坐标设置(设置后点击开始执行生效)")
        y_layout = QVBoxLayout(y_group)
        # 设置较小字体用于此分组内的控件
        small_font = QFont()
        small_font.setPointSize(6)
        y_group.setFont(small_font)
        # CCD 温度
        row1 = QHBoxLayout()
        self.y1_min = QDoubleSpinBox()
        self.y1_min.setRange(-3000, 3000)
        self.y1_min.setDecimals(2)
        self.y1_min.setValue(-101.0)
        self.y1_min.setFont(small_font)
        self.y1_max = QDoubleSpinBox()
        self.y1_max.setRange(-3000, 3000)
        self.y1_max.setDecimals(2)
        self.y1_max.setValue(-99.0)
        self.y1_max.setFont(small_font)
        lbl_y1 = QLabel("CCD Y:")
        lbl_y1.setFont(small_font)
        lbl_y1_min = QLabel("min")
        lbl_y1_min.setFont(small_font)
        lbl_y1_max = QLabel("max")
        lbl_y1_max.setFont(small_font)
        row1.addWidget(lbl_y1)
        row1.addWidget(lbl_y1_min)
        row1.addWidget(self.y1_min)
        row1.addWidget(lbl_y1_max)
        row1.addWidget(self.y1_max)
        y_layout.addLayout(row1)
        # Drive/Dewar 温度
        row2 = QHBoxLayout()
        self.y2_min = QDoubleSpinBox()
        self.y2_min.setRange(-3000, 3000)
        self.y2_min.setDecimals(2)
        self.y2_min.setValue(20.0)
        self.y2_min.setFont(small_font)
        self.y2_max = QDoubleSpinBox()
        self.y2_max.setRange(-3000, 3000)
        self.y2_max.setDecimals(2)
        self.y2_max.setValue(40.0)
        self.y2_max.setFont(small_font)
        lbl_y2 = QLabel("Drive/Dewar Y:")
        lbl_y2.setFont(small_font)
        lbl_y2_min = QLabel("min")
        lbl_y2_min.setFont(small_font)
        lbl_y2_max = QLabel("max")
        lbl_y2_max.setFont(small_font)
        row2.addWidget(lbl_y2)
        row2.addWidget(lbl_y2_min)
        row2.addWidget(self.y2_min)
        row2.addWidget(lbl_y2_max)
        row2.addWidget(self.y2_max)
        y_layout.addLayout(row2)
        # 真空度（以指数输入，例如 -5 到 -1 表示 10^-5 到 10^-1）
        row3 = QHBoxLayout()
        self.y3_min = QDoubleSpinBox()
        self.y3_min.setRange(-12.0, 6.0)
        self.y3_min.setDecimals(2)
        self.y3_min.setValue(-5.0)
        self.y3_min.setFont(small_font)
        self.y3_max = QDoubleSpinBox()
        self.y3_max.setRange(-12.0, 6.0)
        self.y3_max.setDecimals(2)
        self.y3_max.setValue(-1.0)
        self.y3_max.setFont(small_font)
        lbl_y3 = QLabel("真空度Y(指数):")
        lbl_y3.setFont(small_font)
        lbl_y3_min = QLabel("min(10^x)")
        lbl_y3_min.setFont(small_font)
        lbl_y3_max = QLabel("max(10^x)")
        lbl_y3_max.setFont(small_font)
        row3.addWidget(lbl_y3)
        row3.addWidget(lbl_y3_min)
        row3.addWidget(self.y3_min)
        row3.addWidget(lbl_y3_max)
        row3.addWidget(self.y3_max)
        y_layout.addLayout(row3)
        
        file_layout.addLayout(input_layout)
        file_layout.addLayout(output_layout)
        file_layout.addLayout(filename_layout)
        file_layout.addLayout(save_row)
        file_layout.addWidget(y_group)
        
        # 处理按钮
        self.process_btn = QPushButton("开始执行")
        self.process_btn.clicked.connect(self.process_data)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        
        layout.addWidget(file_group)
        layout.addWidget(self.process_btn)
        layout.addWidget(self.progress_bar)
        layout.addStretch()
    
    def browse_input(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择日志文件", "", "Log Files (*.log *.txt);;All Files (*)"
        )
        if file_path:
            self.input_edit.setText(file_path)
            if not self.output_edit.text():
                self.output_edit.setText(os.path.dirname(file_path))
            result_name = os.path.splitext(os.path.basename(file_path))[0]
            self.filename_edit.setText(result_name)
    
    def browse_output(self):
        dir_path = QFileDialog.getExistingDirectory(self, "选择输出目录")
        if dir_path:
            self.output_edit.setText(dir_path)
    
    def _collect_y_limits(self):
        limits = {}
        # temp subplot
        limits['temp'] = (self.y1_min.value(), self.y1_max.value())
        # drive subplot
        limits['drive'] = (self.y2_min.value(), self.y2_max.value())
        # vacuum subplot (log scale) - convert exponents to actual values
        vmin_exp = self.y3_min.value()
        vmax_exp = self.y3_max.value()
        v1 = 10 ** vmin_exp
        v2 = 10 ** vmax_exp
        vmin, vmax = (v1, v2) if v1 < v2 else (v2, v1)
        limits['vacuum'] = (vmin, vmax)
        return limits
    
    def process_data(self):
        input_path = self.input_edit.text().strip()
        output_dir = self.output_edit.text().strip()
        
        if not input_path or not os.path.isfile(input_path):
            QMessageBox.warning(self, "错误", "请选择有效的日志文件")
            return
        if not output_dir:
            output_dir = os.path.dirname(input_path)
        
        self.process_btn.setEnabled(False)
        self.progress_bar.setVisible(True)
        self.progress_bar.setValue(0)
        
        self.processor = DataProcessor('log', input_path, output_dir)
        self.processor.progress_signal.connect(self.progress_bar.setValue)
        self.processor.log_signal.connect(self.log_widget.append)
        self.processor.finished_signal.connect(self.process_finished)
        self.processor.start()
    
    def process_finished(self, result):
        self.process_btn.setEnabled(True)
        self.progress_bar.setVisible(False)
        
        if 'error' in result:
            self.log_widget.append(f"处理失败: {result['error']}")
            return
        
        y_limits = self._collect_y_limits()
        
        if self.save_check.isChecked():
            output_dir = self.output_edit.text().strip() or os.path.dirname(self.input_edit.text().strip())
            filename = self.filename_edit.text().strip() or "result"
            file_path = os.path.join(output_dir, filename + ".png")
            try:
                save_log_plot(result, file_path, y_limits=y_limits)
                self.log_widget.append(f"图片已保存到: {file_path}")
            except Exception as e:
                self.log_widget.append(f"保存失败: {str(e)}")
        
        # 依据当前预览容器大小生成一次适配的预览图
        target_size = self.preview_label.size()
        pix = render_log_qpixmap_sized(result, max(1, target_size.width()), max(1, target_size.height()))
        self.update_preview_pixmap(pix)
        if not self.save_check.isChecked():
            self.log_widget.append("未保存文件（仅预览）")
    
    def update_preview(self, image_path):
        try:
            pix = QPixmap(image_path)
            if pix.isNull():
                self.log_widget.append(f"预览加载失败: {image_path}")
                return
            target_size = self.preview_label.size()
            scaled = pix.scaled(target_size, Qt.AspectRatioMode.KeepAspectRatio if PYQT_VERSION==6 else Qt.KeepAspectRatio, 
                                 Qt.TransformationMode.SmoothTransformation if PYQT_VERSION==6 else Qt.SmoothTransformation)
            self.preview_label.setPixmap(scaled)
        except Exception as e:
            self.log_widget.append(f"预览显示异常: {str(e)}")
    
    def update_preview_pixmap(self, pix: QPixmap):
        try:
            if pix.isNull():
                self.log_widget.append("预览加载失败: 空图像")
                return
            target_size = self.preview_label.size()
            scaled = pix.scaled(target_size, Qt.AspectRatioMode.KeepAspectRatio if PYQT_VERSION==6 else Qt.KeepAspectRatio, 
                                 Qt.TransformationMode.SmoothTransformation if PYQT_VERSION==6 else Qt.SmoothTransformation)
            self.preview_label.setPixmap(scaled)
        except Exception as e:
            self.log_widget.append(f"预览显示异常: {str(e)}")

class FitsDataTab(QWidget):
    """FITS数据处理标签页（直接保存图片，不显示）"""
    def __init__(self, log_widget, preview_label):
        super().__init__()
        self.log_widget = log_widget
        self.preview_label = preview_label
        self.processor = None
        self.setup_ui()
    
    def setup_ui(self):
        layout = QVBoxLayout(self)
        
        # 文件选择组
        file_group = QGroupBox("文件选择")
        file_layout = QVBoxLayout(file_group)
        
        input_layout = QHBoxLayout()
        self.input_edit = QLineEdit()
        self.input_edit.setPlaceholderText("选择FITS文件目录...")
        self.input_btn = QPushButton("浏览")
        self.input_btn.clicked.connect(self.browse_input)
        input_layout.addWidget(QLabel("FITS目录:"))
        input_layout.addWidget(self.input_edit)
        input_layout.addWidget(self.input_btn)
        
        output_layout = QHBoxLayout()
        self.output_edit = QLineEdit()
        self.output_edit.setPlaceholderText("选择输出目录...")
        self.output_btn = QPushButton("浏览")
        self.output_btn.clicked.connect(self.browse_output)
        output_layout.addWidget(QLabel("输出目录:"))
        output_layout.addWidget(self.output_edit)
        output_layout.addWidget(self.output_btn)
        
        # 新增文件名输入框
        filename_layout = QHBoxLayout()
        self.filename_edit = QLineEdit("result")
        filename_layout.addWidget(QLabel("文件名:"))
        filename_layout.addWidget(self.filename_edit)
        
        # 字段配置组
        keys_group = QGroupBox("字段配置（FITS Header 键名）")
        keys_layout = QVBoxLayout(keys_group)
        
        key_row1 = QHBoxLayout()
        self.date_key_edit = QLineEdit("DATE-OBS")
        key_row1.addWidget(QLabel("日期键名:"))
        key_row1.addWidget(self.date_key_edit)
        
        key_row2 = QHBoxLayout()
        self.temp_key_edit = QLineEdit("TEMP")
        key_row2.addWidget(QLabel("温度键名:"))
        key_row2.addWidget(self.temp_key_edit)
        
        key_row3 = QHBoxLayout()
        self.vacuum_key_edit = QLineEdit("VACCUM")
        key_row3.addWidget(QLabel("真空度键名:"))
        key_row3.addWidget(self.vacuum_key_edit)
        
        key_row4 = QHBoxLayout()
        self.frametype_key_edit = QLineEdit("HIERARCH FRAMETYPE")
        key_row4.addWidget(QLabel("图像类型键名:"))
        key_row4.addWidget(self.frametype_key_edit)
        
        keys_layout.addLayout(key_row1)
        keys_layout.addLayout(key_row2)
        keys_layout.addLayout(key_row3)
        keys_layout.addLayout(key_row4)
        # 保存文件开关
        save_row = QHBoxLayout()
        self.save_check = QCheckBox("保存文件")
        self.save_check.setChecked(True)
        save_row.addWidget(self.save_check)

        file_layout.addLayout(input_layout)
        file_layout.addLayout(output_layout)
        file_layout.addLayout(filename_layout)
        file_layout.addWidget(keys_group)
        file_layout.addLayout(save_row)
        
        # 处理按钮
        self.process_btn = QPushButton("开始执行")
        self.process_btn.clicked.connect(self.process_data)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        
        layout.addWidget(file_group)
        layout.addWidget(self.process_btn)
        layout.addWidget(self.progress_bar)
        layout.addStretch()
    
    def browse_input(self):
        dir_path = QFileDialog.getExistingDirectory(self, "选择FITS文件目录")
        if dir_path:
            self.input_edit.setText(dir_path)
            if not self.output_edit.text():
                self.output_edit.setText(dir_path)
    
    def browse_output(self):
        dir_path = QFileDialog.getExistingDirectory(self, "选择输出目录")
        if dir_path:
            self.output_edit.setText(dir_path)
    
    def process_data(self):
        input_path = self.input_edit.text().strip()
        output_dir = self.output_edit.text().strip()
        
        if not input_path or not os.path.isdir(input_path):
            QMessageBox.warning(self, "错误", "请选择有效的FITS文件目录")
            return
        if not output_dir:
            output_dir = input_path
        
        # 采集字段名配置
        fits_keys = {
            'date': self.date_key_edit.text().strip() or 'DATE-OBS',
            'temp': self.temp_key_edit.text().strip() or 'TEMP',
            'vacuum': self.vacuum_key_edit.text().strip() or 'VACCUM',
            'frametype': self.frametype_key_edit.text().strip() or 'HIERARCH FRAMETYPE',
        }
        
        # 生成CSV保存路径（与图片同名，且仅在勾选保存时启用）
        img_filename = self.filename_edit.text().strip() or "result"
        base_name = os.path.splitext(img_filename)[0]
        csv_path = os.path.join(output_dir, base_name + ".csv") if self.save_check.isChecked() else None
        
        self.process_btn.setEnabled(False)
        self.progress_bar.setVisible(True)
        self.progress_bar.setValue(0)
        
        self.processor = DataProcessor('fits', input_path, output_dir, fits_keys=fits_keys, output_csv_path=csv_path)
        self.processor.progress_signal.connect(self.progress_bar.setValue)
        self.processor.log_signal.connect(self.log_widget.append)
        self.processor.finished_signal.connect(self.process_finished)
        self.processor.start()
    
    def process_finished(self, result):
        self.process_btn.setEnabled(True)
        self.progress_bar.setVisible(False)
        
        if 'error' in result:
            self.log_widget.append(f"处理失败: {result['error']}")
            return
        
        if self.save_check.isChecked():
            output_dir = self.output_edit.text().strip() or self.input_edit.text().strip()
            filename = self.filename_edit.text().strip() or "result"
            file_path = os.path.join(output_dir, filename + ".png")
            try:
                save_fits_plot(result, file_path)
                self.log_widget.append(f"图片已保存到: {file_path}")
            except Exception as e:
                self.log_widget.append(f"保存失败: {str(e)}")
        
        # 依据当前预览容器大小生成一次适配的预览图（FITS）
        target_size = self.preview_label.size()
        pix = render_fits_qpixmap(result) if target_size.isEmpty() else render_log_qpixmap_sized(result, max(1, target_size.width()), max(1, target_size.height()))
        self.update_preview_pixmap(pix)
        if not self.save_check.isChecked():
            self.log_widget.append("未保存文件（仅预览）")

    def update_preview(self, image_path):
        try:
            pix = QPixmap(image_path)
            if pix.isNull():
                self.log_widget.append(f"预览加载失败: {image_path}")
                return
            target_size = self.preview_label.size()
            scaled = pix.scaled(target_size, Qt.AspectRatioMode.KeepAspectRatio if PYQT_VERSION==6 else Qt.KeepAspectRatio, 
                                 Qt.TransformationMode.SmoothTransformation if PYQT_VERSION==6 else Qt.SmoothTransformation)
            self.preview_label.setPixmap(scaled)
        except Exception as e:
            self.log_widget.append(f"预览显示异常: {str(e)}")

    def update_preview_pixmap(self, pix: QPixmap):
        try:
            if pix.isNull():
                self.log_widget.append("预览加载失败: 空图像")
                return
            target_size = self.preview_label.size()
            scaled = pix.scaled(target_size, Qt.AspectRatioMode.KeepAspectRatio if PYQT_VERSION==6 else Qt.KeepAspectRatio, 
                                 Qt.TransformationMode.SmoothTransformation if PYQT_VERSION==6 else Qt.SmoothTransformation)
            self.preview_label.setPixmap(scaled)
        except Exception as e:
            self.log_widget.append(f"预览显示异常: {str(e)}")

class TimeFilterThread(QThread):
    """时间筛选后台线程"""
    progress_signal = pyqtSignal(str)
    finished_signal = pyqtSignal(dict)
    error_signal = pyqtSignal(str)
    
    def __init__(self, log_dir, start_time, end_time):
        super().__init__()
        self.log_dir = log_dir
        self.start_time = start_time
        self.end_time = end_time
    
    def run(self):
        try:
            # 获取目录下所有日志文件
            log_files = []
            for ext in ['*.log', '*.txt']:
                log_files.extend(glob.glob(os.path.join(self.log_dir, ext)))
            
            if not log_files:
                self.error_signal.emit("目录中未找到日志文件")
                return
            
            self.progress_signal.emit(f"找到 {len(log_files)} 个日志文件")
            
            # 第一步：根据年月日筛选文件
            filtered_files = self.filter_files_by_date(log_files)
            self.progress_signal.emit(f"根据日期筛选后剩余 {len(filtered_files)} 个文件")
            
            # 第二步：根据具体时间筛选文件内容
            filtered_data = self.filter_content_by_time(filtered_files)
            
            # 返回结果
            result = {
                'success': True,
                'data': filtered_data,
                'file_count': len(filtered_files)
            }
            self.finished_signal.emit(result)
            
        except Exception as e:
            self.error_signal.emit(f"筛选过程中出现错误: {str(e)}")
    
    def filter_files_by_date(self, log_files):
        """根据年月日筛选文件"""
        filtered_files = []
        start_date = self.start_time.date()
        end_date = self.end_time.date()
        
        for log_file in log_files:
            filename = os.path.basename(log_file)
            try:
                # 尝试从文件名提取日期（假设格式为 YYYYMMDD_*.log）
                date_str = filename[:8]
                file_date = datetime.datetime.strptime(date_str, '%Y%m%d').date()
                
                if start_date <= file_date <= end_date:
                    filtered_files.append(log_file)
            except:
                # 如果无法从文件名解析日期，则跳过该文件
                continue
        
        return filtered_files
    
    def filter_content_by_time(self, log_files):
        """根据具体时间筛选文件内容"""
        all_data = []  # 存储所有数据，格式为 (时间, 数据类型, 值)
        
        for i, log_file in enumerate(log_files):
            self.progress_signal.emit(f"处理文件 {i+1}/{len(log_files)}: {os.path.basename(log_file)}")
            
            try:
                with open(log_file, 'r') as fp:
                    lines = fp.readlines()
                
                for line in lines:
                    # 检查时间范围
                    try:
                        if line.startswith('[') and ']' in line:
                            time_str = line[1:line.find(']')]
                            line_time = datetime.datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S.%f')
                            
                            if not (self.start_time <= line_time <= self.end_time):
                                continue
                        else:
                            continue
                    except:
                        continue
                    
                    # 提取温度数据
                    if 'hwStatus.currentTemp' in line:
                        try:
                            temp_val = float(line.split(':')[-1])
                            all_data.append((line_time, 'temp', temp_val))
                        except:
                            continue
                    
                    # 提取真空度数据
                    if 'hwStatus.currentVacuum' in line:
                        try:
                            vacuum_val = float(line.strip().split(':')[-1])
                            all_data.append((line_time, 'vacuum', vacuum_val))
                        except:
                            continue
                    
                    # 提取CCD温度数据
                    if 'Received MCU response:' in line and 'CCD_1:' in line and 'CCD_2:' in line and 'CCD_3:' in line and 'CCD_4:' in line:
                        try:
                            response_part = line.split('Received MCU response:')[1].strip()
                            ccd_parts = response_part.split('&&')
                            
                            ccd1_val = None
                            ccd2_val = None
                            ccd3_val = None
                            ccd4_val = None
                            
                            for part in ccd_parts:
                                if part.startswith('CCD_1:'):
                                    ccd1_val = float(part.split(':')[1])
                                elif part.startswith('CCD_2:'):
                                    ccd2_val = float(part.split(':')[1])
                                elif part.startswith('CCD_3:'):
                                    ccd3_val = float(part.split(':')[1])
                                elif part.startswith('CCD_4:'):
                                    ccd4_val = float(part.split(':')[1])
                            
                            if all(val is not None for val in [ccd1_val, ccd2_val, ccd3_val, ccd4_val]):
                                all_data.append((line_time, 'ccd1', ccd1_val))
                                all_data.append((line_time, 'ccd2', ccd2_val))
                                all_data.append((line_time, 'ccd3', ccd3_val))
                                all_data.append((line_time, 'ccd4', ccd4_val))
                        except:
                            continue
                            
            except Exception as e:
                self.progress_signal.emit(f"处理文件 {log_file} 时出错: {str(e)}")
                continue
        
        # 按时间排序所有数据
        all_data.sort(key=lambda x: x[0])
        
        # 按时间分组数据
        time_groups = {}
        for time, data_type, value in all_data:
            time_str = time.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]  # 保留毫秒
            if time_str not in time_groups:
                time_groups[time_str] = {}
            time_groups[time_str][data_type] = value
        
        # 生成排序后的输出数据
        sorted_output = []
        
        for time_str in sorted(time_groups.keys()):
            group = time_groups[time_str]
            
            # 温度数据行
            temp_line = f"{time_str}: "
            if 'temp' in group:
                temp_line += f"平均温度={group['temp']}°C "
            if 'ccd1' in group:
                temp_line += f"CCD1={group['ccd1']}°C "
            if 'ccd2' in group:
                temp_line += f"CCD2={group['ccd2']}°C "
            if 'ccd3' in group:
                temp_line += f"CCD3={group['ccd3']}°C "
            if 'ccd4' in group:
                temp_line += f"CCD4={group['ccd4']}°C"
            
            # 只有当有温度数据时才添加这一行
            if 'temp' in group or 'ccd1' in group or 'ccd2' in group or 'ccd3' in group or 'ccd4' in group:
                sorted_output.append(temp_line.strip())
            
            # 真空度数据行
            if 'vacuum' in group:
                vacuum_line = f"{time_str}: 真空度={group['vacuum']} Pa"
                sorted_output.append(vacuum_line)
        
        return sorted_output

class MainWindow(QMainWindow):
    """主窗口（不展示图形，仅用于选择与日志显示）"""
    def __init__(self):
        super().__init__()
        self.time_filter_thread = None
        self.setup_ui()
        
    def setup_ui(self):
        self.setWindowTitle("Mephito Mosaic Camera: 温度真空度数据可视化工具")
        self.setGeometry(100, 100, 1100, 700)
        
        # 创建中央部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 分割器：左侧控制，右侧日志
        splitter = QSplitter()
        
        # 左侧：标签页 + 时间筛选功能
        left_widget = QWidget()
        left_layout = QVBoxLayout(left_widget)
        
        # 标签页
        self.tab_widget = QTabWidget()
        
        # 日志组件
        self.log_widget = QTextEdit()
        self.log_widget.setReadOnly(True)
        # 预览组件
        self.preview_label = QLabel()
        self.preview_label.setMinimumHeight(480)  # 从360增加到480，以适应3个子图
        self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter if PYQT_VERSION==6 else Qt.AlignCenter)
        self.preview_label.setText("无预览")
        
        # 创建标签页
        self.log_tab = LogDataTab(self.log_widget, self.preview_label)
        self.fits_tab = FitsDataTab(self.log_widget, self.preview_label)
        
        self.tab_widget.addTab(self.log_tab, "日志数据处理")
        self.tab_widget.addTab(self.fits_tab, "FITS数据处理")
        
        # 时间筛选功能区域
        time_filter_group = QGroupBox("日志文件时间筛选")
        time_filter_layout = QVBoxLayout(time_filter_group)
        
        # 目录选择
        dir_layout = QHBoxLayout()
        self.dir_edit = QLineEdit()
        self.dir_edit.setPlaceholderText("选择日志文件目录...")
        self.dir_btn = QPushButton("浏览")
        self.dir_btn.clicked.connect(self.browse_log_directory)
        dir_layout.addWidget(QLabel("日志目录:"))
        dir_layout.addWidget(self.dir_edit)
        dir_layout.addWidget(self.dir_btn)
        
        # 开始时间
        start_time_layout = QHBoxLayout()
        self.start_time_edit = QDateTimeEdit()
        self.start_time_edit.setDateTime(QDateTime.currentDateTime().addDays(-1))
        self.start_time_edit.setDisplayFormat("yyyy-MM-dd hh:mm:ss")
        start_time_layout.addWidget(QLabel("开始时间:"))
        start_time_layout.addWidget(self.start_time_edit)
        
        # 结束时间
        end_time_layout = QHBoxLayout()
        self.end_time_edit = QDateTimeEdit()
        self.end_time_edit.setDateTime(QDateTime.currentDateTime())
        self.end_time_edit.setDisplayFormat("yyyy-MM-dd hh:mm:ss")
        end_time_layout.addWidget(QLabel("结束时间:"))
        end_time_layout.addWidget(self.end_time_edit)
        
        # 执行按钮
        self.filter_btn = QPushButton("执行时间筛选")
        self.filter_btn.clicked.connect(self.execute_time_filter)
        
        # 清理日志按钮
        self.clear_btn = QPushButton("清理日志")
        self.clear_btn.clicked.connect(self.clear_log)
        
        # 按钮布局
        button_layout = QHBoxLayout()
        button_layout.addWidget(self.filter_btn)
        button_layout.addWidget(self.clear_btn)
        
        time_filter_layout.addLayout(dir_layout)
        time_filter_layout.addLayout(start_time_layout)
        time_filter_layout.addLayout(end_time_layout)
        time_filter_layout.addLayout(button_layout)
        
        # 添加到左侧布局
        left_layout.addWidget(self.tab_widget)
        left_layout.addWidget(time_filter_group)
        
        # 右侧：日志区域
        right_layout = QVBoxLayout()
        right_layout.addWidget(QLabel("图像预览"))
        right_layout.addWidget(self.preview_label)
        right_layout.addWidget(QLabel("处理日志"))
        right_layout.addWidget(self.log_widget)
        right_widget = QWidget()
        right_widget.setLayout(right_layout)
        
        # 添加到分割器
        splitter.addWidget(left_widget)
        splitter.addWidget(right_widget)
        left_widget.setMinimumWidth(320)
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 4)  # 从3增加到4，给右侧更多空间
        splitter.setSizes([380, 900])  # 从720增加到900，给预览区域更多空间
        
        # 主布局
        layout = QVBoxLayout(central_widget)
        layout.addWidget(splitter)

        # 设置窗口图标
        try:
            if os.path.exists('icon.png'):
                icon = QIcon('logo.png')
                self.setWindowIcon(icon)
                print("窗口图标设置成功")
            else:
                print("")
        except Exception as e:
            print(f"设置窗口图标失败: {e}")
    
    def browse_log_directory(self):
        """浏览日志文件目录"""
        dir_path = QFileDialog.getExistingDirectory(self, "选择日志文件目录")
        if dir_path:
            self.dir_edit.setText(dir_path)
    
    def execute_time_filter(self):
        """执行时间筛选功能"""
        log_dir = self.dir_edit.text().strip()
        if not log_dir or not os.path.isdir(log_dir):
            QMessageBox.warning(self, "错误", "请选择有效的日志文件目录")
            return
        
        # 获取时间范围
        if PYQT_VERSION == 5:
            start_time = self.start_time_edit.dateTime().toPyDateTime()
            end_time = self.end_time_edit.dateTime().toPyDateTime()
        else:
            start_time = self.start_time_edit.dateTime()
            end_time = self.end_time_edit.dateTime()
        
        if start_time >= end_time:
            QMessageBox.warning(self, "错误", "开始时间必须早于结束时间")
            return
        
        # 开始筛选
        self.log_widget.append("开始执行时间筛选...")
        self.filter_btn.setEnabled(False)
        
        # 创建并启动后台线程
        self.time_filter_thread = TimeFilterThread(log_dir, start_time, end_time)
        self.time_filter_thread.progress_signal.connect(self.log_widget.append)
        self.time_filter_thread.finished_signal.connect(self.on_time_filter_finished)
        self.time_filter_thread.error_signal.connect(self.on_time_filter_error)
        self.time_filter_thread.start()
    
    def on_time_filter_finished(self, result):
        """时间筛选完成回调"""
        try:
            filtered_data = result['data']
            
            # 输出结果到日志
            self.log_widget.append(f"时间筛选完成！")
            self.log_widget.append(f"筛选结果：")
            self.log_widget.append(f"- 总数据行数：{len(filtered_data)} 行")
            
            # 统计各类数据点数量
            temp_count = 0
            vacuum_count = 0
            ccd_count = 0
            
            for line in filtered_data:
                if '平均温度=' in line:
                    temp_count += 1
                if 'CCD1=' in line or 'CCD2=' in line or 'CCD3=' in line or 'CCD4=' in line:
                    ccd_count += 1
                if '真空度=' in line:
                    vacuum_count += 1
            
            self.log_widget.append(f"- 温度数据点：{temp_count} 个")
            self.log_widget.append(f"- CCD温度数据点：{ccd_count} 个")
            self.log_widget.append(f"- 真空度数据点：{vacuum_count} 个")
            
            # 显示时间范围
            if filtered_data:
                first_time = filtered_data[0].split(':')[0]
                last_time = filtered_data[-1].split(':')[0]
                self.log_widget.append(f"- 数据时间范围：{first_time} 到 {last_time}")
            
            # 输出所有筛选出的数据
            self.log_widget.append(f"\n=== 详细数据（按时间排序）===")
            
            # 输出所有数据
            for data_line in filtered_data:
                self.log_widget.append(data_line)
                
        except Exception as e:
            self.log_widget.append(f"处理结果时出现错误: {str(e)}")
        finally:
            self.filter_btn.setEnabled(True)
    
    def on_time_filter_error(self, error_msg):
        """时间筛选错误回调"""
        self.log_widget.append(f"筛选错误: {error_msg}")
        self.filter_btn.setEnabled(True)
    
    def clear_log(self):
        """清理日志内容"""
        self.log_widget.clear()
        self.log_widget.append("日志已清理。")


def main():
    app = QApplication(sys.argv)
    app.setApplicationName("CCD温度真空度数据处理工具")
    app.setStyle('Fusion')
    window = MainWindow()
    # window.setWindowIcon(QIcon('./icon.png'))
    window.show()
    sys.exit(app.exec() if PYQT_VERSION == 6 else app.exec_())

if __name__ == "__main__":
    main()